Astuces pour bien programmer **************************** Les variables ============= Créez des variables ------------------- .. panels:: :column: col-lg-10 p-2 **ASTUCE** : Si le résultat d'une expression est important, créez une variable pour le stocker. .. panels:: :column: col-lg-10 p-2 **ASTUCE** : Si une expression se repète dans le code, créez une variable pour stocker cette information. Voici un code qui vérifie si la position du héros est à l'extérieur de la zone d'affichage : .. code-block:: cpp if ( x + cos(theta) * y - 400 > 217) ... if ( x + cos(theta) * y - 400 < 0 ) ... Il est très difficile de déterminer la signification du test effectué. Par contre, en créant : * Une variable HeroPos stockant la position du héros donnée par l'expression : x + cos(theta) * y - 400 * Une variable LARG_AFF correspondant à la largeur de la zone d'affichage On obtient une version du code plus facile à lire. Les commentaires sont données à titre indicatif, ils pourraient être omis. .. code-block:: cpp HeroPos.x = x + cos(theta) * y - 400; // coordonnée du héros à l’écran if ( HeroPos.x > LARG_AFF ) .. // teste si le héros est visible à l'écran if ( HeroPos.x < 0 ) .. Evitez les variables redondantes -------------------------------- Pour gérer une liste de momies, un programmeur crée deux variables : .. code-block:: cpp int nbMomies = 0; // nombre de momies bool EstVide = true; // aucune momie dans la liste Où est le problème de conception ? Techniquement, ce programme peut fonctionner avec ce choix de conception. Cependant, la variable EstVide est en redondance avec la variable nbMomies. Ainsi, chaque fois que le nombre d'éléments change, la variable nbMomies est modifiée. Mais, il faut aussi penser à mettre à jour la variable EstVide et le risque d'oublier cette variable secondaire existe. Ce choix de conception est dangereux, mieux vaut choisir une unique variable pour stocker la quantité d'éléments présents et recalculer à la volée le test permettant de déterminer si aucune momie existe en créant une fonction dédiée. Préférez les enum aux numériques -------------------------------- Vous avez sûrement déjà rencontré du code ressemblant à l'exemple suivant : .. code-block:: cpp int Obj = Tab[k][j]; if ( Obj == 1 ) .. else if ( (Obj == 0) || (Obj == 4)) ... else if (Obj == 7) ... Il est utile pour améliorer la lisibilité d'utiliser une énumération pour remplacer les littéraux présents dans les tests : .. code-block:: cpp enum class Obj { Tresor, Porte, Grille, Fleche }; Obj Objet = Tab[k][j]; if ( Objet == Obj::Tresor ) .. else if ( (Objet == Obj::Porte) || (Objet == Obj::Grille)) ... else if (Objet == Objet::Fleche) La lecture du code s'en trouve facilitée : ce programme peut être repris et modifié plus tard sans aucun souci par n'importe quel membre de l'équipe. Organisez vos données ===================== Le scénario qui fait perdre du temps ------------------------------------ Nous allons présenter cette problématique où un programmeur, nommé Tom, crée des variables sans les structurer. * Jour 1 : Pour modéliser le héros du jeu, Tom crée la variable HerosPV initialisée à la valeur 200. * Jour 4 : Un autre jour, Tom programme la gestion des combats entre le héros et les monstres. Si un monstre réussit à toucher le joueur, celui-ci perd des points de vie. Tom, au moment où il cherche à coder le retrait des points de vie, n'arrive plus à retrouver le nom de la variable en question. Il cherche dans le code rapidement mais ne la trouve pas. Tom en déduit alors qu'il ne l'a jamais créée et il met alors en place une nouvelle variable VieDuHeros initialisée à la valeur 200. Il utilise cette variable pour programmer la gestion des combats. Au final, le programme fonctionne correctement. Cependant, la variable HerosPV est une variable zombie, elle existe dans le programme sans être utilisée. * Jour 6 : Lors d'une prochaine séance, Tom cherche à implémenter une potion de soin. Par manque de chance, en recherchant dans le programme il retrouve la variable initiale HerosPV. Il utilise donc cette variable pour gérer les effets de la potion de soin. Tom écrit un code correct, sans erreur de logique. Seulement, lors des tests, il se rend compte que la potion de soin ne fait pas augmenter les points de vie du joueur.. * Jour 7 : Tom déprime, il a vérifié son code, tout semble correct. Il arrête de coder et va jouer à LOL. * Jour 8 : Lors de la phase de Debug, Tom vérifie que la variable HerosPV varie comme prévu lorsque le héros utilise sa potion de vie. Il faudra beaucoup de temps à Tom pour recoller les morceaux du puzzle et trouver l'origine du quiproquo. Que de temps perdu... Comment éviter ce problème ? * Il ne s'agit pas ici d'un souci de logique : tout le code est écrit correctement. * La lisibilité est de qualité car les noms des variables sont explicites. * Pas d'erreur de syntaxe car le programme ne s'est pas arrêté avec un message d'erreur. On parle de bug certes car le programme ne se comporte pas comme il devrait. Mais, il s'agit ici plutôt d'un problème de conception au niveau de l'organisation du code. En effet, en créant des variables un peu partout dans le code, Tom a fini par se mélanger les pinceaux. Regrouper --------- Pour structurer les variables, il est important, de localiser les variables de votre jeu au même endroit. Cela restreint la zone de recherche à quelques lignes. Il est aussi adroit de regrouper les variables par thème : toutes les variables qui concernent le héros, puis celles des méchants, et ensuite viennent celles du décor par exemple. Idéalement les structures sont particulièrement indiquée pour rassembler toutes ces informations : .. code-block:: cpp struct Pos { int x; int y; }; struct _Heros { Pos P; int HPointDeVie; }; struct VarPartie { // héros _Heros Hero; // Méchants Pos PosMomie; // position momie // objets Pos Posclef; // position de la clef }; Les fonctions ============= Identifier une fonction ----------------------- Prenons un cas pratique. Supposons que nous devons modéliser un aventurier dans un labyrinthe. Celui-ci se déplace et nous devons déterminer s'il passe à proximité de certains pièges. Pour cela, nous notons les coordonnées des pièges P1x, P1y, P2x, P2y, P3x et P3y et les coordonnées du héros Hx et Hy. Le code qui détermine si le héros est proche d'un piège est le suivant : .. code-block:: cpp if ( pow(P1x-Hx,2) + pow(P1y-Hy,2) < 20*20 ) return true; if ( pow(P2x-Hx,2) + pow(P2y-Hy,2) < 20*20 ) return true; if ( pow(P3x-Hx,2) + pow(P3y-Hy,2) < 20*20 ) return true; return false .. panels:: :column: col-lg-10 p-2 **ASTUCE** : Si des lignes de code se répètent, c'est un signe indiquant qu'une fonction doit être créée. .. note:: Il peut vous sembler étrange d'écrire une fonction pour remplacer "juste" trois lignes de code. Cependant, cela ne doit pas vous arrêter. Pour créer la fonction, il suffit donc de passer en arguments les coordonnées du piège et du héros : .. code-block:: cpp bool ProcheHeros(int x, int y, int Hx, int Hy) : return pow(x-Hx,2) + pow(y-Hy,2) < 20*20; Améliorer une fonction ---------------------- Dans le paragraphe précédent, nous avons construit une fonction qui teste la proximité entre un élément du jeu et notre personnage. Cependant, cette fonction est trop spécialisée. En effet, on teste la proximité entre un piège et le héros. Mais en fait, cette fonction peut tout aussi bien être utilisée pour tester la proximité entre deux éléments. Nous renommons la fonction pour qu'elle soit réutilisable pour d'autres traitements : .. code-block:: cpp bool Proximité(int x1, int y1, int x2, int y2) : return pow(x1-x2,2) + pow(y1-y2,2) < 20*20; On peut encore améliorer notre résultat. En effet, il reste un littéral dans cette fonction permettant de régler le seuil de proximité. Là encore, cela dépend de la taille des objets à l'écran. Il faut rendre cette fonction la plus adaptable possible. Ainsi, nous allons passer le seuil en paramètre : .. code-block:: cpp bool Proximité(int x1, int y1, int x2, int y2, int distmin) : return pow(x1-x2,2) + pow(y1-y2,2) < distmin*distmin; La fonction peut être maintenant réutilisée pour diverses configurations : le héros et les momies, le héros et les pièges, les flèches et les monstres… Cette fonction n'utilise pas de variables globales. Le traitement qu'elle effectue est donc entièrement indépendant du reste du programme. Ainsi, cette fonction pourrait être importée dans un autre programme et être réutilisée telle quelle. Restructurer son code ===================== Il est normal d'écrire plusieurs lignes de code et ensuite de chercher à les structurer en fonctions. Cependant, le mauvais réflexe consiste à sélectionner, un peu au hasard, des portions de code pour créer vos fonctions. Certes on arrive à un résultat, mais souvent pas plus satisfaisant. Alors on essaye autre chose, mais ce n'est guère mieux. Pour structurer plusieurs lignes de code à posteriori, vous devez identifier les rôles des fonctions et seulement ensuite répartir les lignes de code suivant le rôle de chaque fonction. .. panels:: :column: col-lg-10 p-2 **CONSEIL :** Lorsque vous restructurez votre code en fonctions ; il faut d'abord identifier chaque fonction ainsi que leur rôle et seulement ensuite répartir les lignes de code et non l'inverse.